/***************************************************************************
 *   Copyleft 2006-2007 by												   *
 *	 Iwanicki Krzysztof													   *
 *   <gamma.mini.pw.edu.pl/~iwanickik>									   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 ***************************************************************************/
#include "./BSplineSurface.h"

/**************************************************************
* Constructors                                                *
***************************************************************/
BSplineSurface::BSplineSurface(cInt iU, cInt iV): Mesh() {
	Mesh::mSurfaceColor = RGB(247, 160, 8);
	mSurfacesVisible = true;
	mIsPeriodic = false;
	mU = iU;
	mV = iV;
}

/*virtual*/ BSplineSurface::~BSplineSurface() {
	for (cInt i = 0; i < mSurfaceCurves.GetSize(); i++)
		delete mSurfaceCurves[i];
}
    
/**************************************************************
* Operators                                                   *
***************************************************************/


/**************************************************************
* Other helpful functions                                     *
***************************************************************/
/*virtual*/ cVoid BSplineSurface::Render(Renderer& renderer, Matrix4D& matrix, ProjectionPlane& plane) {
	if (mSurfacesVisible) {
		for (cInt i = 0; i < mSurfaceCurves.GetSize(); i++) {
			mSurfaceCurves[i]->Render(renderer, matrix, plane, mSurfaceColor);
		}
	}
	Mesh::Render(renderer, matrix, plane);
}

/*virtual*/ cVoid BSplineSurface::Render(Renderer& renderer, Matrix4D& matrix, ProjectionPlane& plane, cWinColor color) {
	if (mSurfacesVisible) {
		for (cInt i = 0; i < mSurfaceCurves.GetSize(); i++) {
			mSurfaceCurves[i]->Render(renderer, matrix, plane, color);
		}
	}
	Mesh::Render(renderer, matrix, plane, color);
}

/*virtual*/ cVoid BSplineSurface::UpdateMeshPoint(cInt iRow, cInt iCol, Point4D point) {
	Mesh::UpdateMeshPoint(iRow, iCol, point);
	CalculateSurface();
}

cVoid BSplineSurface::SwitchSurfaceVisibility() {
	mSurfacesVisible = !mSurfacesVisible;
}

cVoid BSplineSurface::CreateSurface(AbstractCurve* curve1, AbstractCurve* curve2) {
	Mesh::Create(curve1, curve2);
	CalculateSurface();
}

/*virtual*/ cVoid BSplineSurface::SwitchPeriodic() {
	Mesh::SwitchPeriodic();
	CalculateSurface();
}

/*virtual*/ cVoid BSplineSurface::SetPeriodic(cBool bPeriodic) {
	if (bPeriodic != mIsPeriodic) {
		Mesh::SetPeriodic(bPeriodic);
		CalculateSurface();
	}
}

cVoid BSplineSurface::CalculateSurface() {
	if (mMesh.GetColSize() < 4 || mMesh.GetRowSize() < 4) {
		return;
	}
	ClearMeshLines();
	

	DynamicArray2D<cBool> tab;
	//FloodFill(tab);
	MakeMeshLines();//add param tab

	if (mIsHole) {
		BasicCurve *p = new BasicCurve();
		for (cInt i = 0; i < mHole.GetSize(); i++) {
			p->AddPoint(CalculatePoint(mHole[i].mX, mHole[i].mY));
		}
		mSurfaceCurves.Add(p);
	}
}

cVoid BSplineSurface::ClearMeshLines() {
	for (cInt i = 0; i < mSurfaceCurves.GetSize(); i++) {
		delete mSurfaceCurves[i];
	}
	mSurfaceCurves.Clear();
}

cVoid BSplineSurface::MakeMeshLines() {
	cFloat u, v, du = 1.0f/(mU), dv = 1.0f/(mV);
	BasicCurve *p = cNull;
	Point4D point;

	u = 0.0f;
	for (cInt i = 0; i < mU + 1; i++, u += du) {
		v = 0.0f;
		p = cNull;
		for (cInt j = 0; j < mV + 1; j++, v += dv) {
			if (p == cNull) {
				p = new BasicCurve();
				mSurfaceCurves.Add(p);
				if (j > 0) {
					point = CalculatePoint(u, v - dv);
					p->AddPoint(point);
				}
			}
			point = CalculatePoint(u, v);
			p->AddPoint(point);
		}
	}

	v = 0.0f;
	for (cInt j = 0; j < mV + 1; j++, v += dv) {
		u = 0.0f;
		p = cNull;
		for (cInt i = 0; i < mU + 1; i++, u += du) {
			if (p == cNull) {
				p = new BasicCurve();
				mSurfaceCurves.Add(p);
				if (i > 0) {
					point = CalculatePoint(u - du, v);
					p->AddPoint(point);
				}
			}
			point = CalculatePoint(u, v);
			p->AddPoint(point);
		}
	}
}

Point4D BSplineSurface::CalculatePoint(cFloat u, cFloat v) {
	Point4D p, temp;
	cFloat Nu[4], Nv[4], du, dv;
	cInt nu, nv, x, y;
	cInt row = mMesh.GetRowSize();
	cInt col = mMesh.GetColSize();

	if (mIsPeriodic && (v < 0.0f || v > 1.0f)) {
		v -= floor(v);
	}

	du = 1.0f / (row - 3);
	if (u >= 1.0f) {
		nu = row - 4;
	} else {
		nu = (cInt) (u/du);
	}
	u = u / du - nu;
	CalculateN(u, Nu);

	dv = 1.0f / (mIsPeriodic ? col : col - 3);
	if (v >= 1.0f) {
		nv = mIsPeriodic ? col - 1 : col - 4;
	} else {
		nv = (cInt)(v/dv);
	}
	v = v / dv - nv;
	CalculateN(v, Nv);

	for (cInt i = 0; i < 4; i++) {
		for (cInt j = 0; j < 4; j++) {
			x = nu + i;
			y = nv + j;
			if (mIsPeriodic) {
				y %= col;
			}
			temp = mMesh(x, y).mPoint * Nu[i] * Nv[j];
			p = p + Point4D(temp.mX, temp.mY, temp.mZ);
			p.mW = 1.0f;
		}
	}

	return p;
}

cVoid BSplineSurface::CalculateN(cFloat t, cFloat *N, cInt n) {
	cFloat A[3], B[3], saved, term;
	cFloat T[] = {-3.0f, -2.0f, -1.0f, 0.0f, 1.0f, 2.0f, 3.0f, 4.0f};
	cInt i = 3;

	N[0] = 1.0f;
	for (cInt j = 0; j < n; j++) {
		A[j] = T[i + j + 1] - t;
		B[j] = t - T[i - j];
		saved = 0.0f;
		for (cInt k = 0; k <= j; k++) {
			term = N[k] / (A[k] + B[j - k]);
			N[k] = saved + A[k] * term;
			saved = B[j - k] * term;
		}
		N[j + 1] = saved;
	}
}

/*virtual*/ cVoid BSplineSurface::ChangeU(cInt iU) {
	Mesh::ChangeU(iU);
	CalculateSurface();
}

/*virtual*/ cVoid BSplineSurface::ChangeV(cInt iV) {
	Mesh::ChangeV(iV);
	CalculateSurface();
}

/*virtual*/ cChar* BSplineSurface::GetName() {
	return "BSplineSurface";
}

/*virtual*/ cVoid BSplineSurface::DisplayInfo(Renderer& renderer) {
	cChar pcBuff [MAX_BUF_SIZE];
	cInt iLineNr = 0;

	sprintf_s(pcBuff, "B-Spline Surface Info:");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		true, true, true, mSurfaceColor);
	sprintf_s(pcBuff, "Closed :");	
	renderer.DrawString(H_TXT_SPACER, iLineNr * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "%s", mIsPeriodic ? "true" : "false");	
	renderer.DrawString(H_TXT_SPACER * 10, iLineNr++ * V_TXT_SPACER, pcBuff, 
		true, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "U : ");	
	renderer.DrawString(H_TXT_SPACER, iLineNr * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "%3i", mU);	
	renderer.DrawString(H_TXT_SPACER * 3, iLineNr++ * V_TXT_SPACER, pcBuff, 
		true, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "V : ");	
	renderer.DrawString(H_TXT_SPACER, iLineNr * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "%3i", mV);	
	renderer.DrawString(H_TXT_SPACER * 3, iLineNr++ * V_TXT_SPACER, pcBuff, 
		true, true, true, Color::ToWinColor(WHITE));
	iLineNr++;
	sprintf_s(pcBuff, "[Ctrl + P] - Switch Close Mode");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "[Ctrl/Alt + J] - Increase/Decrease U");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "[Ctrl/Alt + K] - Increase/Decrease V");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "[Ctrl + N] - Show/Hide Control Points Coordinates");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "[Ctrl + B] - Show/Hide Control Points");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "[Ctrl + V] - Show/Hide Mesh");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "[Ctrl + C] - Show/Hide Surface");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "Space - Deselect Points");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
		sprintf_s(pcBuff, "LMB & Drag - Move Selected Points");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
}

/**************************************************************
* Trimming functions                                          *
***************************************************************/
Point4D BSplineSurface::CalculateUDerivative(cFloat u, cFloat v) {
	Point4D p;
	cFloat Nu1[4], Nu[3], Nv[4], du, dv;
	cInt nu, nv, x, y;
	cInt row = mMesh.GetRowSize();
	cInt col = mMesh.GetColSize();

	if (mIsPeriodic && (v < 0.0f || v > 1.0f)) {
		v -= floor(v);
	}

	du = 1.0f / (row - 3);
	if (u >= 1.0f) {
		nu = row - 4;
	} else {
		nu = (cInt)(u/du);
	}
	u = u / du - nu;
	CalculateN(u, Nu, 2);

	dv = 1.0f / (mIsPeriodic ? col : col - 3);
	if (v >= 1.0f) {
		nv = mIsPeriodic ? col - 1 : col - 4;
	} else {
		nv = (cInt) (v/dv);
	}
	v = v / dv - nv;
	CalculateN(v, Nv);

	for (cInt i = -1; i < 3; i++) {
		Nu1[i + 1] = (i < 0 ? 0.0f : Nu[i]/du) - ((i >= 2) ? 0.0f : Nu[i + 1]/du);
	}

	for (cInt i = 0; i < 4; i++) {
		for (cInt j = 0; j < 4; j++) {
			x = nu + i;
			y = nv + j;
			if (mIsPeriodic) {
				y %= col;
			}
			p = p + mMesh(x, y).mPoint * Nu1[i] * Nv[j];
		}
	}

	return p;
}

Point4D BSplineSurface::CalculateVDerivative(cFloat u, cFloat v) {
	Point4D p;
	cFloat Nv1[4], Nu[4], Nv[3], du, dv;
	cInt nu, nv, x, y;
	cInt row = mMesh.GetRowSize();
	cInt col = mMesh.GetColSize();

	if (mIsPeriodic && (v < 0.0f || v > 1.0f)) {
		v -= floor(v);
	}

	du = 1.0f / (row - 3);
	if (u >= 1.0f) {
		nu = row - 4;
	} else {
		nu = (cInt) (u / du);
	}
	u = u / du - nu;
	CalculateN(u, Nu);

	dv = 1.0f / (mIsPeriodic ? col : col - 3);
	if (v >= 1.0f) {
		nv = mIsPeriodic ? col - 1 : col - 4;
	} else {
		nv = (cInt) (v / dv);
	}
	v = v / dv - nv;
	CalculateN(v, Nv, 2);

	for (cInt i = -1; i < 3; i++) {
		Nv1[i + 1] = (i < 0 ? 0.0f : Nv[i] / dv) - ((i >= 2) ? 0.0f : Nv[i + 1] / dv);
	}

	for (cInt i = 0; i < 4; i++) {
		for (cInt j = 0; j < 4; j++) {
			x = nu + i;
			y = nv + j;
			if (mIsPeriodic) {
				y %= col;
			}
			p = p + mMesh(x, y).mPoint * Nu[i] * Nv1[j];
		}
	}

	return p;
}

Point4D BSplineSurface::CalculateNormalVersor(cFloat u, cFloat v) {
	Point4D n = CalculateUDerivative(u, v) * CalculateVDerivative(u, v);
	return n / n.Abs();
}

cVoid BSplineSurface::SetHole(DynamicArray<Vector2D> &h) {
	mHole = h;
	mIsHole = true;
	CalculateSurface();
}

cVoid BSplineSurface::DeleteHole() {
	mHole.Clear();
	mIsHole = false;
	CalculateSurface();
}

cVoid BSplineSurface::SetIsHoleInterior(cBool v) {
	mIsHoleInterior = v;
	CalculateSurface();
}

cBool BSplineSurface::GetIsHoleInterior() {
	return mIsHoleInterior;
}

/**************************************************************
* Serializing to XML functions                                *
***************************************************************/
/*virtual*/ TiXmlElement* BSplineSurface::Serialize(TiXmlElement* parent) {
	TiXmlElement* elem = new TiXmlElement("Object3D");
	parent->LinkEndChild(elem);
	elem->SetAttribute("type", GetName());
	elem->SetAttribute("U", mU);
	elem->SetAttribute("V", mV);
	Mesh::Serialize(elem);

	return elem;
}